/* todo: move all funcs to arch.c! */
#include <arch.h>
#include <io_port.h>
#include <conio.h>
#include <pic.h>
#include <exc.h>
#include <mman.h>
#include <irq.h>
#include <timer.h>
#include <atomic.h>
#include <context.h>
#include <paging.h>
#include <cpu.h>
#include <string.h>

extern uint32_t __end;
extern uint32_t __code;
extern uint32_t __data;
extern uint32_t __bss;

extern int main(int, char **);
extern sysinfo_t sys;

struct tss_def sys_tss;		/* TSS of system, one per CPU */
static int isem;

void i386set_seg(void *entry,
		 uint32_t base, uint32_t limit, uchar rights, uchar gran)
{
    *((uint32_t *) entry) = (limit & 0xFFFF) | ((base & 0xFFFF) << 16);

    *((uint32_t *) (((char *) entry) + 4)) =
	((base & 0x00FF0000) >> 16) | (base & 0xFF000000) |
	(rights << 8) | ((gran & 0xF0) << 16) | ((limit & 0x000F000) << 4);
}

void i386set_taskgate(void *entry, uint16_t selector, uchar rights)
{
    *((uint32_t *) entry) = selector << 16;
    *((uint32_t *) (((char *) entry) + 4)) = (0x05 | (rights & 0xF0)) << 8;
}

void i386ltr(uint16_t selector)
{
    __asm__ __volatile__("ltr %0"::"r"(selector));
}

void add_idtirq(uint32_t * IDT, uint32_t num, uint32_t sel, void *func)
{
    IDT[2 * num + 1] = (((uint32_t) func) & 0xFFFF0000) | 0x00008E00;
    IDT[2 * num] = (((uint32_t) func) & 0x0000FFFF) | (sel << 16);
}

void add_idtuirq(uint32_t * IDT, uint32_t num, uint32_t sel, void *func)
{
    IDT[2 * num + 1] = (((uint32_t) func) & 0xFFFF0000) | 0x0000EE00;
    IDT[2 * num] = (((uint32_t) func) & 0x0000FFFF) | (sel << 16);
}

void add_idtexc(uint32_t * IDT, uint32_t num, uint32_t sel, void *func)
{
    IDT[2 * num + 1] = (((uint32_t) func) & 0xFFFF0000) | 0x00008F00;
    IDT[2 * num] = (((uint32_t) func) & 0x0000FFFF) | (sel << 16);
}

char *gdt;
char *idt;

void systables_init()
{
    gdt = (char *) &gdtable;
    idt = (char *) &idtable;
    memset(gdt, 0, 1024);
    kprintf("GDTable at 0x%p\n", gdt);
    kprintf("IDTable at 0x%p\n", idt);

    gdt_init();
    idt_init();
    i386lgdt((uint32_t)gdt);
    i386lidt((uint32_t)idt);
    systss_init();
}

void systss_init()
{
    memset(&sys_tss, 0, sizeof(struct tss_def));

    sys_tss.ss0 = sys_tss.ss = SEL_KDATA;
    sys_tss.esp0 = sys_tss.esp = alloc_page() + PAGE_SIZE;

    i386set_seg(gdt + SEL_KTSS,
		(uint32_t) & sys_tss, sizeof(struct tss_def),
		i386rPRESENT | i386rDPL0 | i386rTSS, i386g32BIT);
    i386ltr(SEL_KTSS);
}

void irq_enable()
{
    if (isem <= 1) {
	isem = 0;
	asm("sti");
    } else {
	isem--;
    }
}

void irq_disable()
{
    asm("cli");
    isem++;
}

EXCEPTION_HANDLER(unhandle_irq)
{
    kprintf("Unknown interrupt!\n");
    asm("cli");
    asm("hlt");
}

/* init new IDTable */
void idt_init()
{
    int i;

    for (i = 0; i < 256; i++) {
		add_idtexc((uint32_t *) idt, i, SEL_KCODE, unhandle_irq);
    }
}

/* init new GDTable */
void gdt_init()
{
    i386set_seg(gdt + SEL_KCODE,
		0, 0xFFFFF,
		i386rPRESENT | i386rDPL0 | i386rCODE,
		i386g4K | i386g32BIT);

    i386set_seg(gdt + SEL_KDATA,
		0, 0xFFFFF,
		i386rPRESENT | i386rDPL0 | i386rDATA,
		i386g4K | i386g32BIT);

    i386set_seg(gdt + 24,
		0, 0xFFFFF,
		i386rPRESENT | i386rDPL3 | i386rCODE,
		i386g4K | i386g32BIT);

    i386set_seg(gdt + 32,
		0, 0xFFFFF,
		i386rPRESENT | i386rDPL3 | i386rDATA,
		i386g4K | i386g32BIT);
}

int arch_init(multiboot_info_t *info, unsigned magic)
{

    kprintf("x86 Kernel started...\n");
    isem = 0;
//    irq_disable();
    sys.mbi = info;
    
    pic_init();
    systables_init();
    init_exc();
    timer_init();
    irq_init();
    mman_init();
    
    /* parsing cmd line */
    if (CHECK_FLAG (info->flags, 2)) {
		kprintf("kernel cmdline: %s\n", sys.mbi->cmdline);
    }
    
    return ((int)main(0, (char**)0));
}
